//
//  WCSyntaxHighlighter.m
//  WabbitCode
//
//  Created by William Towe on 2/12/09.
//  Copyright 2009 Revolution Software. All rights reserved.
//

#import "WCSyntaxHighlighter.h"
#import "WCTextView.h"
#import "WCMacros.h"
#import "WCAppController.h"
#import "NSTextView+WCExtensions.h"
#import "NSString+WCExtensions.h"
#import "WCProject.h"
#import <RegexKit/RegexKit.h>
#import "WCBuildTargetsGroup.h"
#import "WCBuildTarget.h"
#import "WCBuildConfiguration.h"


RKRegex *directivesRegex = nil;
RKRegex *conditionsRegex = nil;
RKRegex *operationalCodesRegex = nil;
RKRegex *registersRegex = nil;
RKRegex *labelsRegex = nil;
RKRegex *preProcessorRegex = nil;
RKRegex *stringsRegex = nil;
RKRegex *baseTenNumbersRegex = nil;
RKRegex *baseSixteenNumbersRegex = nil;
RKRegex *baseTwoNumbersRegex = nil;
RKRegex *commentsRegex = nil;
RKRegex *multilineCommentsRegex = nil;

@interface WCSyntaxHighlighter (Private)
- (void)highlightRange:(NSRange)range inTextView:(WCTextView *)textView;
@end

@implementation WCSyntaxHighlighter
#pragma mark *** Subclass Overrides ***
+ (void)initialize {
	const RKCompileOption options = (RKCompileMultiline);
	
	directivesRegex = [[RKRegex alloc] initWithRegexString:@"\\.(?:db|dw|end|org|byte|word|fill|block|addinstr|echo|error|list|nolist|equ|show|option|seek)\\b" options:options];
	conditionsRegex = [[RKRegex alloc] initWithRegexString:@"\\b(?:nz|nv|nc|po|pe|c|p|m|n|z|v)\\b" options:options];
	operationalCodesRegex = [[RKRegex alloc] initWithRegexString:@"\\b(?:adc|add|and|bit|call|ccf|cpdr|cpd|cpir|cpi|cpl|cp|daa|dec|di|djnz|ei|exx|ex|halt|im|inc|indr|ind|inir|ini|in|jp|jr|lddr|ldd|ldir|ldi|ld|neg|nop|or|otdr|otir|outd|outi|out|pop|push|res|reti|retn|ret|rla|rlca|rlc|rld|rl|rra|rrca|rrc|rrd|rr|rst|sbc|scf|set|sla|sll|sra|srl|sub|xor)\\b" options:options|RKCompileCaseless];
	registersRegex = [[RKRegex alloc] initWithRegexString:@"\\b(?:ixh|iyh|ixl|iyl|sp|af|pc|bc|de|hl|ix|iy|a|f|b|c|d|e|h|l|r|i)\\b" options:options];
	labelsRegex = [[RKRegex alloc] initWithRegexString:@"[A-z0-9_!?.]+" options:options];
	// to make #define color entire line (?:define.*$))
	// ^\\s*(?:(?:#define(?:\\s+\\w+)*?)|(?:#\\w+\\b))
	// ^\\s*#(?:(?:include|ifndef|ifdef|if|else|endif|undefine|undef|macro|endmacro|import|defcont)|(?:define(?:\\s+\\w+)*?))
	preProcessorRegex = [[RKRegex alloc] initWithRegexString:@"^\\s*#(?:(?:include|ifndef|ifdef|if|else|endif|undefine|undef|macro|endmacro|import|defcont)|(?:define(?:\\s+\\w+)*?))" options:options|RKCompileCaseless];
	stringsRegex = [[RKRegex alloc] initWithRegexString:@"\".*?\"" options:options];
	baseTenNumbersRegex = [[RKRegex alloc] initWithRegexString:@"\\b[0-9]+\\b" options:options];
	baseSixteenNumbersRegex = [[RKRegex alloc] initWithRegexString:@"(?:\\$[0-9a-fA-F]+\\b)|(?:[^$%]\\b[0-9a-fA-F]+h\\b)" options:options];
	baseTwoNumbersRegex = [[RKRegex alloc] initWithRegexString:@"(?:%[01]+\\b)|(?:[^$%]\\b[01]+b)" options:options];
	commentsRegex = [[RKRegex alloc] initWithRegexString:@";+.*$" options:options];
	// the RKCompileDotAll option must be used so multiline comments will match past a newline char
	multilineCommentsRegex = [[RKRegex alloc] initWithRegexString:@"(?:#comment.*?#endcomment)|(?:#comment.*)" options:options|RKCompileDotAll];
}

- (id)init {
	if (!(self = [super init]))
		return nil;
	
	return self;
}

- (void)dealloc {
	[NSObject cancelPreviousPerformRequestsWithTarget:self];
	
	[super dealloc];
}

#pragma mark *** Public Methods ***
+ (WCSyntaxHighlighter *)sharedInstance; {
	static id _sharedInstance = nil;
	if (!_sharedInstance) {
		_sharedInstance = [[[self class] alloc] init];
	}
	return _sharedInstance;
}


- (void)unhighlightTextView:(WCTextView *)textView; {
	[[textView layoutManager] removeTemporaryAttribute:NSForegroundColorAttributeName forCharacterRange:[[textView string] wholeRange]];
}

- (void)highlightCurrentLineInTextView:(WCTextView *)textView; {
	if (!NSUserDefaultsBoolForKey(kWCPrefsEditorSyntaxHighlightingEnabledKey))
		return;
	
	NSString *string = [textView string];
	
	if (![string length])
		return;
	
	[self highlightRange:[string lineRangeForRange:[textView selectedRange]] inTextView:textView];
}

- (void)highlightTextView:(WCTextView *)textView; {
	if (!NSUserDefaultsBoolForKey(kWCPrefsEditorSyntaxHighlightingEnabledKey))
		return;
	else if (!textView)
		return;
	
	NSString *string = [textView string];
	
	if (![string length])
		return;
	
	[self highlightRange:[textView visibleRange] inTextView:textView];
}

- (void)highlightRange:(NSRange)hRange inTextView:(WCTextView *)textView; {
	NSString *string = [textView string];
	NSLayoutManager *lm = [textView layoutManager];
	NSRange vRange = hRange;
	//NSRange vRange = [textView visibleRange];
	NSString *sString = [string substringWithRange:vRange];
	
	[lm removeTemporaryAttribute:NSForegroundColorAttributeName forCharacterRange:vRange];
	
	NSColor *color = nil;
	RKEnumerator *enumerator = nil;
	
	BOOL colorPreProcessorCommands = NSUserDefaultsBoolForKey(kWCPrefsEditorPreProcessorCommandsEnabledKey);
	if (colorPreProcessorCommands) {
		// pre processor commands
		enumerator = [[[RKEnumerator alloc] initWithRegex:preProcessorRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorPreProcessorCommandsColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorConditionalRegistersEnabledKey)) {
		// conditional registers
		enumerator = [[[RKEnumerator alloc] initWithRegex:conditionsRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorConditionalRegistersColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorOperationalCodesEnabledKey)) {
		// operational codes
		enumerator = [[[RKEnumerator alloc] initWithRegex:operationalCodesRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorOperationalCodesColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorOtherRegistersEnabledKey)) {
		// other registers
		enumerator = [[[RKEnumerator alloc] initWithRegex:registersRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorOtherRegistersColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorBaseTenNumbersEnabledKey)) {
		// base ten numbers
		enumerator = [[[RKEnumerator alloc] initWithRegex:baseTenNumbersRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorBaseTenNumbersColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorBaseSixteenNumbersEnabledKey)) {
		// base sixteen numbers
		enumerator = [[[RKEnumerator alloc] initWithRegex:baseSixteenNumbersRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorBaseSixteenNumbersColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorBaseTwoNumbersEnabledKey)) {
		// base two number
		enumerator = [[[RKEnumerator alloc] initWithRegex:baseTwoNumbersRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorBaseTwoNumbersColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	BOOL colorLabels = NSUserDefaultsBoolForKey(kWCPrefsEditorLabelsEnabledKey);
	BOOL colorEquates = NSUserDefaultsBoolForKey(kWCPrefsEditorEquatesEnabledKey);
	if (colorLabels || colorEquates || colorPreProcessorCommands) {
		// labels and equates
		enumerator = [[[RKEnumerator alloc] initWithRegex:labelsRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorLabelsColorKey);
		NSColor *eColor = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorEquatesColorKey);
		NSColor *mColor = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorPreProcessorCommandsColorKey);
		
		BOOL labelsAndMacrosAreCaseSensitive = [[[[[textView project] buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
		
		NSSet *projectLabels = [[textView project] projectLabels];
		NSSet *projectEquates = [[textView project] projectEquates];
		NSSet *projectDefines = [[textView project] projectDefines];
		NSSet *projectMacros = [[textView project] projectMacros];
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			NSString *labelOrEquate = (labelsAndMacrosAreCaseSensitive)?[sString substringWithRange:*range]:[[sString substringWithRange:*range] lowercaseString];
			
			if (colorEquates && [projectEquates containsObject:labelOrEquate])
				[lm addTemporaryAttribute:NSForegroundColorAttributeName value:eColor forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
			else if (colorLabels && [projectLabels containsObject:labelOrEquate])
				[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
			else if (colorPreProcessorCommands && ([projectMacros containsObject:labelOrEquate] || [projectDefines containsObject:labelOrEquate]))
				[lm addTemporaryAttribute:NSForegroundColorAttributeName value:mColor forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorDirectivesEnabledKey)) {
		// assembler directives
		enumerator = [[[RKEnumerator alloc] initWithRegex:directivesRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorDirectivesColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorStringsEnabledKey)) {
		// strings
		enumerator = [[[RKEnumerator alloc] initWithRegex:stringsRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorStringsColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	
	if (NSUserDefaultsBoolForKey(kWCPrefsEditorCommentsEnabledKey)) {
		// comments
		enumerator = [[[RKEnumerator alloc] initWithRegex:commentsRegex string:sString] autorelease];
		color = NSUserDefaultsKeyedUnarchivedObjectForKey(kWCPrefsEditorCommentsColorKey);
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
		
		enumerator = [[[RKEnumerator alloc] initWithRegex:multilineCommentsRegex string:string] autorelease];
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttribute:NSForegroundColorAttributeName value:color forCharacterRange:*range];
		}
	}
	
	/*
	if ([textView shouldHighlightWord]) {
		
		NSRange vRange = oldRange;
		NSString *sString = [[textView string] substringWithRange:vRange];
		NSLayoutManager *lm = [textView layoutManager];
		 
		RKRegex *regex = [[[RKRegex alloc] initWithRegexString:[NSString stringWithFormat:@"\\b%@\\b", [textView wordToHighlight]] options:RKCompileNoOptions] autorelease];
		RKEnumerator *enumerator = [[[RKEnumerator alloc] initWithRegex:regex string:sString] autorelease];
		
		while ([enumerator nextRanges] != NULL) {
			NSRangePointer range = [enumerator currentRanges];
			[lm addTemporaryAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[NSColor colorWithCalibratedRed:0.75 green:0.75 blue:0.0 alpha:1.0], NSBackgroundColorAttributeName, [NSColor whiteColor], NSForegroundColorAttributeName, nil] forCharacterRange:NSMakeRange(vRange.location+range->location, range->length)];
		}
	}
	*/
}
#pragma mark Accessors

@end
